/*
* jQuery.eraser v0.5.2
* makes any image or canvas erasable by the user, using touch or mouse input
* https://github.com/boblemarin/jQuery.eraser
*
* Usage:
*
* $('#myImage').eraser(); // simple way
*
* $('#canvas').eraser( {
*   size: 20, // define brush size (default value is 40)
*   completeRatio: .65, // allows to call function when a erased ratio is reached (between 0 and 1, default is .7 )
*   completeFunction: myFunction // callback function when complete ratio is reached
* } );
*
* $('#image').eraser( 'clear' ); // erases all canvas content
*
* $('#image').eraser( 'reset' ); // revert back to original content
*
* $('#image').eraser( 'size', 80 ); // change the eraser size
*
* $('#image').eraser( 'enable/disable' ); // enable or disable erasing
*
* $('#image').eraser( 'enabled' ); // returns whether the eraser is enabled
*
*
* https://github.com/boblemarin/jQuery.eraser
* http://minimal.be/lab/jQuery.eraser/
*
* Copyright (c) 2010 boblemarin emeric@minimal.be http://www.minimal.be
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

(function($){
	var methods = {

		init: function(options) {
			return this.each(function(){
				var $this = $(this),
					data = $this.data('eraser');

				if (!data) {

					var handleImage = function() {
						var $canvas = $('<canvas/>'),
							canvas = $canvas.get(0),
							ctx = canvas.getContext('2d'),

							// calculate scale ratio for high DPI devices
							// http://www.html5rocks.com/en/tutorials/canvas/hidpi/
							devicePixelRatio = window.devicePixelRatio || 1,
							backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
								ctx.mozBackingStorePixelRatio ||
								ctx.msBackingStorePixelRatio ||
								ctx.oBackingStorePixelRatio ||
								ctx.backingStorePixelRatio || 1,
							scaleRatio = devicePixelRatio / backingStoreRatio,

							realWidth = $this.width(),
							realHeight = $this.height(),
							width = realWidth * scaleRatio,
							height = realHeight * scaleRatio,
							pos = $this.offset(),
							enabled = (options && options.enabled === false) ? false : true,
							size = ((options && options.size) ? options.size : 40) * scaleRatio,
							completeRatio = (options && options.completeRatio) ? options.completeRatio : .7,
							completeFunction = (options && options.completeFunction) ? options.completeFunction : null,
							progressFunction = (options && options.progressFunction) ? options.progressFunction : null,
							zIndex = $this.css('z-index') == "auto"?1:$this.css('z-index'),
							parts = [],
							colParts = Math.floor(width / size),
							numParts = colParts * Math.floor(height / size),
							n = numParts,
							that = $this[0];

						// replace target with canvas
						$this.after($canvas);
						canvas.id = that.id;
						canvas.className = that.className;
						canvas.width = width;
						canvas.height = height;
						canvas.style.width = realWidth.toString() + "px";
						canvas.style.height = realHeight.toString() + "px";
						ctx.drawImage(that, 0, 0, width, height);
						$this.remove();

						// prepare context for drawing operations
						ctx.globalCompositeOperation = 'destination-out';
						ctx.strokeStyle = 'rgba(255,0,0,255)';
						ctx.lineWidth = size;

						ctx.lineCap = 'round';
						// bind events
						$canvas.bind('mousedown.eraser', methods.mouseDown);
						$canvas.bind('touchstart.eraser', methods.touchStart);
						$canvas.bind('touchmove.eraser', methods.touchMove);
						$canvas.bind('touchend.eraser', methods.touchEnd);

						// reset parts
						while(n--) parts.push(1);

						// store values
						data = {
							posX: pos.left,
							posY: pos.top,
							touchDown: false,
							touchID: -999,
							touchX: 0,
							touchY: 0,
							ptouchX: 0,
							ptouchY: 0,
							canvas: $canvas,
							ctx: ctx,
							w: width,
							h: height,
							scaleRatio: scaleRatio,
							source: that,
							size: size,
							parts: parts,
							colParts: colParts,
							numParts: numParts,
							ratio: 0,
							enabled: enabled,
							complete: false,
							completeRatio: completeRatio,
							completeFunction: completeFunction,
							progressFunction: progressFunction,
							zIndex: zIndex
						};
						$canvas.data('eraser', data);

						// listen for resize event to update offset values
						$(window).resize(function() {
							var pos = $canvas.offset();
							data.posX = pos.left;
							data.posY = pos.top;
						});
					}

					if (this.complete && this.naturalWidth > 0) {
						handleImage();
					} else {
						//this.onload = handleImage;
						$this.on('load', handleImage);
					}
				}
			});
		},

		touchStart: function(event) {
			var $this = $(this),
				data = $this.data('eraser');

			if (!data.touchDown) {
				var t = event.originalEvent.changedTouches[0],
					tx = t.pageX - data.posX,
					ty = t.pageY - data.posY;
				tx *= data.scaleRatio;
				ty *= data.scaleRatio;

				if (data.enabled) {
					methods.evaluatePoint(data, tx, ty);
				}

				data.touchDown = true;
				data.touchID = t.identifier;
				data.touchX = tx;
				data.touchY = ty;
				event.preventDefault();
			}
		},

		touchMove: function(event) {
			var $this = $(this),
				data = $this.data('eraser');

			if (data.touchDown) {
				var ta = event.originalEvent.changedTouches,
					n = ta.length;
				while (n--) {
					if (ta[n].identifier == data.touchID) {
						var tx = ta[n].pageX - data.posX,
							ty = ta[n].pageY - data.posY;
						tx *= data.scaleRatio;
						ty *= data.scaleRatio;

						if (data.enabled) {
							methods.evaluatePoint(data, tx, ty);
							data.ctx.beginPath();
							data.ctx.moveTo(data.touchX, data.touchY);
							data.ctx.lineTo(tx, ty);
							data.ctx.stroke();
							$this.css({"z-index":$this.css('z-index')==data.zIndex?parseInt(data.zIndex)+1:data.zIndex});
						}

						data.touchX = tx;
						data.touchY = ty;

						event.preventDefault();
						break;
					}
				}
			}
		},

		touchEnd: function(event) {
			var $this = $(this),
				data = $this.data('eraser');

			if ( data.touchDown ) {
				var ta = event.originalEvent.changedTouches,
					n = ta.length;
				while( n-- ) {
					if ( ta[n].identifier == data.touchID ) {
						data.touchDown = false;
						event.preventDefault();
						break;
					}
				}
			}
		},

		evaluatePoint: function(data, tx, ty) {
			if (!data.enabled) return;
			var p = Math.floor(tx/data.size) + Math.floor( ty / data.size ) * data.colParts;

			if ( p >= 0 && p < data.numParts ) {
				data.ratio += data.parts[p];
				data.parts[p] = 0;
				if (!data.complete) {
					p = data.ratio/data.numParts;
					if ( p >= data.completeRatio ) {
						data.complete = true;
						if ( data.completeFunction != null ) data.completeFunction();
					} else {
						if ( data.progressFunction != null ) data.progressFunction(p);
					}
				}
			}

		},

		mouseDown: function(event) {
			var $this = $(this),
				data = $this.data('eraser'),
				tx = event.pageX - data.posX,
				ty = event.pageY - data.posY;
			tx *= data.scaleRatio;
			ty *= data.scaleRatio;

			data.touchDown = true;
			data.touchX = tx;
			data.touchY = ty;

			if (data.enabled) {
				methods.evaluatePoint( data, tx, ty );

				data.ctx.beginPath();
				data.ctx.moveTo(data.touchX-1, data.touchY);
				data.ctx.lineTo(data.touchX, data.touchY);
				data.ctx.stroke();
			}

			$this.bind('mousemove.eraser', methods.mouseMove);
			$(document).bind('mouseup.eraser', data, methods.mouseUp);
			event.preventDefault();
		},

		mouseMove: function(event) {
			var $this = $(this),
				data = $this.data('eraser'),
				tx = event.pageX - data.posX,
				ty = event.pageY - data.posY;
			tx *= data.scaleRatio;
			ty *= data.scaleRatio;

			if (data.enabled) {
				methods.evaluatePoint( data, tx, ty );
				data.ctx.beginPath();
				data.ctx.moveTo( data.touchX, data.touchY );
				data.ctx.lineTo( tx, ty );
				data.ctx.stroke();
				$this.css({"z-index":$this.css('z-index')==data.zIndex?parseInt(data.zIndex)+1:data.zIndex});
			}

			data.touchX = tx;
			data.touchY = ty;

			event.preventDefault();
		},

		mouseUp: function(event) {
			var data = event.data,
				$this = data.canvas;

			data.touchDown = false;
			$this.unbind('mousemove.eraser');
			$(document).unbind('mouseup.eraser');
			event.preventDefault();
		},

		clear: function() {
			var $this = $(this),
				data = $this.data('eraser');

			if (data) {
				data.ctx.clearRect(0, 0, data.w, data.h);
				var n = data.numParts;
				while(n--) data.parts[n] = 0;
				data.ratio = data.numParts;
				data.complete = true;
				if (data.completeFunction != null) data.completeFunction();
			}
		},

		enabled: function() {
			var $this = $(this),
				data = $this.data('eraser');

			if (data && data.enabled) {
				return true;
			}
			return false;
		},

		enable: function() {
			var $this = $(this),
				data = $this.data('eraser');

			if (data) {
				data.enabled = true;
			}
		},

		disable: function() {
			var $this = $(this),
				data = $this.data('eraser');

			if (data) {
				data.enabled = false;
			}
		},

		size: function(value) {
			var $this = $(this),
				data = $this.data('eraser');

			if (data && value) {
				data.size = value;
				data.ctx.lineWidth = value;
			}
		},

		reset: function() {
			var $this = $(this),
				data = $this.data('eraser');

			if (data) {
				data.ctx.globalCompositeOperation = 'source-over';
				data.ctx.drawImage( data.source, 0, 0, data.w, data.h);
				data.ctx.globalCompositeOperation = 'destination-out';
				var n = data.numParts;
				while (n--) data.parts[n] = 1;
				data.ratio = 0;
				data.complete = false;
				data.touchDown = false;
			}
		},

		progress: function() {
			var $this = $(this),
				data = $this.data('eraser');

			if (data) {
				return data.ratio/data.numParts;
			}
			return 0;
		}

	};

	$.fn.eraser = function(method) {
		if (methods[method]) {
			return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
		} else if (typeof method === 'object' || ! method) {
			return methods.init.apply(this, arguments);
		} else {
			$.error('Method ' +  method + ' does not yet exist on jQuery.eraser');
		}
	};
})(jQuery);